/**
 * Refer https://wiki.cordys.com/display/Vanenburgapp/Auto+suggestion+API+for+CPF+form+fields for details on
 * usage
 */
if (!window["vg"]) {
    vg = {};
}

if (!vg.visualization) {
    vg.visualization = {};
}

vg.visualization.AutoCompleteMenu = (function() {
    function AutoComplete(inputObj) {
        this.isGrid = inputObj.isGrid || false;
        if (this.isGrid) {
            if (!(inputObj.tableObject || inputObj.columnNumber)) {
                throw "tableObject or columnNumber property missing in the input object";
            }
            this.tableObject = inputObj.tableObject;
            var model = window[this.tableObject.model];
            var insertHandler = bindEvent(this._insertHandler, this);
            model.addListener("xforms-onafterinsert", insertHandler);
            var deleteRefreshHandler = bindEvent(this._deleteAndRefreshHandler, this);
            model.addListener("xforms-onafterdelete", deleteRefreshHandler);
            model.addListener("xforms-onafterrefresh", deleteRefreshHandler);
            this.columnNumber = inputObj.columnNumber;
        }
        this.wsModel = inputObj.model;
        this.automaticFetch = !inputObj.manualFetch;
        this.menuHeight = addPx(inputObj.height);
        this.menuWidth = addPx(inputObj.width);
        this.menuTop = addPx(inputObj.top);
        this.menuLeft = addPx(inputObj.left);
        this.inputFldId = this.isGrid ? this.columnNumber : inputObj.inputFldId;
        this.menuDivId = this.inputFldId + "_menu_div";
        this.wsInputObj = this._cacheInputElements(inputObj.wsInputObj);
        this.includeLeadingSpaces = !! (inputObj.includeLeadingSpaces);
        this.includeTrailingSpaces = !! (inputObj.includeTrailingSpaces);
        this.searchRegEx = inputObj.searchExpression;
        this.entryConfigArray = inputObj.entryConfigArray;
        this.fieldFillValue = inputObj.fieldFillValue;
        this.singleFire = inputObj.singleFire || false;
        this.wsFired = false;
        this.selectionHandler = null;
        this.cacheObj = {};
        this._setKeyEvents();
        this.previousKeyCode = undefined;
    };

    AutoComplete.prototype = (function() {

        /**
         * For each object entry in the array, caches the HTML input element corresponding to the id mentioned in the
         * fldId property of the object. If the object has the isColumn property true, then the fldId is considered as
         * the column number of the table denoted by tableObject. Then the input elements in column are cached.
         * @param {Object} obj - if called without obj, wsInputObj is taken by default
         */
        var _cacheInputElements = function(arr) {
            arr = arr || this.wsInputObj;
            for (var i = 0; i < arr.length; i++) {
                if (this.isGrid && arr[i].isColumn) {
                    arr[i].element = this._cacheFullColumns(arr[i].fldId);
                } else {
                    arr[i].element = window[arr[i].fldId];
                }
            }
            return arr;
        }

        /**
         * To cache the input elements in a column from the table object
         * @param {Object} columnNumber - number of the column taking the leftmost column as number 1
         * @param {Object} keyPressHandler - if provided with a function, it will be attached to the onkeyup and onkeydown
         * events of the elements
         */
        var _cacheFullColumns = function(columnNumber, keyPressHandler) {
            var rows = this.tableObject.getRows();
            var cacheArray = [null];
            for (var i = 0; i < rows.length; i++) {
                this._cacheSingleColumn(columnNumber, cacheArray, keyPressHandler, rows[i]);
            }
            return cacheArray;
        }

        /**
         * To cache a column of a particular row in the table object
         * @param {Object} columnNumber - Number of the column to be cached
         * @param {Object} cacheArray - The array to which the element has to be cached
         * @param {Object} keyPressHandler - Keypress event handler if required
         * @param {Object} row - If provided then the column will be taken from this row. Else the column will be
         * taken from the currently active row
         */
        var _cacheSingleColumn = function(columnNumber, cacheArray, keyPressHandler, row) {
            if (!row) {
                var rowNumber = this.tableObject.getIndex();
                row = this.tableObject.getRows()[rowNumber - 1];
            }
            var columnInput = row.cells[columnNumber].getElementsByTagName("input")[0];
            if (keyPressHandler) {
                columnInput.onkeyup = keyPressHandler;
                columnInput.onkeydown = keyPressHandler;
            }
            cacheArray.push(columnInput);
            return cacheArray;
        }

        /**
         * Trims leading spaces from the text passed. Edit this function to change the
         * patterns in the displayed text
         * @param {Object} str
         */
        var _trim = function(str) {
            var trimExp = new RegExp((this.includeLeadingSpaces ? "" : "\\s*") + "(.*?)" + (this.includeTrailingSpaces ? "" : "\\s*"));
            return str.replace(trimExp, "$1");
        }

        /**
         * Sets the keyup and keydown events for an input field if the menu is just for a single field and not a grid
         */
        var _setSingleFieldKeyEvents = function() {
            this.fillElmt = window[this.inputFldId];
            this.fillElmt.onkeydown = this.keyEventHandler;
            this.fillElmt.onkeyup = this.keyEventHandler;
        }

        /**
         * Sets the keyup and keydown events for the column of a grid
         */
        var _setGridKeyEvents = function() {
            this.columnsArray = this._cacheFullColumns(this.columnNumber, this.keyEventHandler);
        }

        /**
         * Sets the keyup and keydown events for the input field
         */
        var _setKeyEvents = function() {
            this.keyEventHandler = bindEvent(this._keyEvent, this);
            this[this.isGrid ? "_setGridKeyEvents" : "_setSingleFieldKeyEvents"]();
        }

        /**
         * Handler for processing the key press events in the input field. The event object is passed by the browser.
         * @param {Object} eventObject
         */
        var _keyEvent = function(eventObject) {
            eventObject && cordys.stopPropagation(eventObject);
            var eventType = eventObject.type;
            var charCode = eventObject.which ? eventObject.which : eventObject.keyCode;
            this.isGrid && this._setInputElement();
            var controlKeyMap = {
                "40": true,
                "13": true,
                "38": true,
                "9": true
            };
            console.log("char code : " + charCode);
            console.log("previous char code : " + this.previousKeyCode);
            console.log("isMenuVisible : " + AutoComplete.isMenuVisible);
            console.log("automaticFetch : " + this.automaticFetch);
            console.log("Event Type : " + eventType);
            /*if(this.automaticFetch && eventType == "keyup" && this.charCode == "13" && this.previousKeyCode == '13') {
		    } else*/
            if ((this.automaticFetch && !controlKeyMap[charCode] && eventType == "keydown") || (!this.automaticFetch && charCode == "13" && eventType == "keydown" && (!AutoComplete.isMenuVisible || (AutoComplete.isMenuVisible && !controlKeyMap[this.previousKeyCode])))) {
                var searchText = this.fillElmt.value;
                console.log("menu not visible and triggering process for '" + searchText);
                this._triggerSearchProcess(searchText);
            } else if (AutoComplete.isMenuVisible && controlKeyMap[charCode] && eventType == "keydown") {
                console.log("menu visible and navigating");
                eventObject && cordys.preventDefault(eventObject);
                this._menuNavigate(charCode);
            }
            this.previousKeyCode = charCode;
            this.fillElmt.focus();
            console.log("------------------ focus set to field ------------------");
        }

        /**
         * To set the input element in case of grids
         */
        var _setInputElement = function() {
            this.fillElmt = this.columnsArray[this.tableObject.getIndex()];
        }

        var _menuNavigate = function(charCode) {
            if (charCode == "40") {
                this.currentMenuItem.nextSibling && this._changeMenuEntry(this.currentMenuItem.nextSibling, "down");
            } else if (charCode == "38") {
                this.currentMenuItem.previousSibling && this._changeMenuEntry(this.currentMenuItem.previousSibling, "up");
            } else {
                this._selectionHandler();
            }
            this._setScrolls();
        }

        /**
         * Creates a new cache entry for the search text provided
         * @param {Object} searchText - the cache key with which the cache entry can be retrieved
         */
        var _newCacheObject = function(searchText) {
            var currentCache = this.currentCacheObj = this.cacheObj[this._getCurrentCacheKey(searchText)] = {};
            currentCache.dataCache = {};
            AutoComplete.eventCaptureLayer.onclick = bindEvent(this.clearMenu, this);
            var menuNode = currentCache.entryNode = document.createElement("div");
            menuNode.className = "menu-div";
            menuNode.style.height = this.menuHeight;
            menuNode.style.width = this.menuWidth;
            menuNode.style.left = this.menuLeft;
            menuNode.style.zIndex = "10001";
        }

        /**
         * To calculate the location of the menu from the top based on the position of the currently selected row
         */
        var _calculateMenuTop = function() {
            var rowNumber = this.tableObject.getIndex();
            var headerHeight = this.tableObject.tHead.offsetHeight;
            var scrollTop = this.tableObject.parentNode.scrollTop;
            var currentRowTop = this.tableObject.rows[rowNumber + 1].offsetTop;
            var firstRowBottom = Number(this.menuTop.replace("px", ""));
            return (currentRowTop + firstRowBottom - headerHeight - scrollTop) + "px";
        }

        /**
         * Returns the current cache key formed from the search text and all the webservice input element values
         * @param {Object} searchText
         */
        var _getCurrentCacheKey = function(searchText) {
            var key = "";
            var arr = this.wsInputObj;
            for (var i = 0; i < arr.length; i++) {
                if (this.isGrid && arr[i].isColumn) {
                    var rowNumber = this.tableObject.getIndex();
                    key += arr[i].element[rowNumber].value;
                } else {
                    if (!this.inputFldId || (this.inputFldId && (this.inputFldId != arr[i].fldId))) {
                        key += arr[i].element.value;
                    }
                }
            }
            return (key + searchText);
        }

        /**
         * Retrieves an entry from the search cache when provided with the search text
         * @param {Object} searchText
         */
        var _getCachedEntry = function(searchText) {
            var currentCacheKey = this._getCurrentCacheKey(searchText);
            var entry = this.cacheObj[currentCacheKey];
            return (entry ? entry : false);
        }

        /**
         * Returns true if cached
         * @param {Object} searchText
         */
        var _isCached = function(searchText) {
            return !!this._getCachedEntry(searchText);
        }

        /**
         * Handler that handles the response of the search webservice. Checks whether the ws has to be fired in single
         * fire mode and calls parse function accordingly
         */
        var _searchResponse = function(response, searchText) {
            //console.log("searchText in _searchResponse : " + searchText);
            //console.log("response recieved for : " + eventObject.userArguments.searchText);
            this.response = response;
            this.wsFired = this.singleFire;
            this._parseResponse(response /*this.response*/ , searchText); //eventObject.userArguments.searchText);
        }

        /**
         * Parses and caches the search response
         * @param {Object} result - webservice response
         */
        var _parseResponse = function(result, searchText) {
            //console.log("searchText in _parseResponse : " + searchText);
            //console.log("Reponse for " + searchText + " : \n" + cordys.getXML(result) + "\n\n\n");
            this._newCacheObject(searchText);
            searchText = this._trim(searchText);
            var searchTextRegexp = new RegExp(searchText, "i");
            var dataNodes = {};
            result = this._getRearrangedResult(result);
            for (var i = 0; i < this.entryConfigArray.length; i++) {
                var currentTagName = this.entryConfigArray[i].tagName;
                dataNodes[currentTagName] = cordys.selectXMLNodes(result, ".//*[local-name()='" + currentTagName + "']");
            }
            var numEntries = dataNodes[currentTagName].length;
            var dataCacheEntry = {};
            for (index = 0; index < numEntries; index++) {
                var currentEntryDiv = this._createEntryDiv();
                var innerHTML = "";
                for (i = 0; i < this.entryConfigArray.length; i++) {
                    var currentTagObj = this.entryConfigArray[i];
                    currentTagName = currentTagObj.tagName;
                    var currentTagValue = cordys.getTextContent(dataNodes[currentTagName][index]);
                    if (currentTagObj["isDisplayValue"]) innerHTML += (currentTagValue + ", ");
                    dataCacheEntry[currentTagName] = currentTagValue;
                }
                if (searchTextRegexp.test(innerHTML)) {
                    currentEntryDiv.innerHTML = formatHTML(innerHTML, searchText);
                    this.currentCacheObj.entryNode.appendChild(currentEntryDiv);
                    this._pushToDataCache(dataCacheEntry, this._getText(currentEntryDiv));
                }
            }
            this._fillFromCache(searchText);
        }

        /**
         * To rearrange the list of tuples in the order of ascending length of menu entry text
         */
        var _getRearrangedResult = function(result) {
            var reResult = cordys.loadXMLDocument("<result></result>");
            var resultNode = cordys.selectXMLNode(reResult, ".//*[local-name() = 'result']");
            var tuples = cordys.selectXMLNodes(result, ".//*[local-name() = 'tuple']");
            var tuplesArr = _copyObject(tuples, []);
            tuplesArr.sort((function(that) {
                var tagArr = []
                for (i = 0; i < that.entryConfigArray.length; i++) {
                    var currentTagObj = that.entryConfigArray[i];
                    if (currentTagObj["includeInSort"]) {
                        tagArr.push(currentTagObj["tagName"]);
                    }
                }
                return function(node1, node2) {
                    var str1 = "",
                        str2 = "";
                    for (var j = 0; j < tagArr.length; j++) {
                        str1 += cordys.getTextContent(cordys.selectXMLNode(node1, ".//*[local-name() = '" + tagArr[j] + "']"));
                        str2 += cordys.getTextContent(cordys.selectXMLNode(node2, ".//*[local-name() = '" + tagArr[j] + "']"));
                    }
                    return str1.length - str2.length;
                }
            })(this));
            for (var i = 0; i < tuplesArr.length; i++) {
                resultNode.appendChild(tuplesArr[i]);
            }
            return reResult;
        }

        /**
         * To push the dataCacheEntry to the dataCache
         * @param {Object} src - source object
         * @param {Object} dest - destination object
         */
        var _pushToDataCache = function(dataCacheEntry, entryKey) {
            var dest = this.currentCacheObj.dataCache[entryKey] = {};
            _copyObject(dataCacheEntry, dest);
        }

        /**
         * To copy properties from source object to destination object
         * @param {Object} source
         * @param {Object} destination
         */
        var _copyObject = function(source, destination) {
            for (var i in source) {
                if (source.hasOwnProperty(i)) {
                    destination[i] = source[i];
                }
            }
            return destination;
        }

        /**
         * Setting the scrolls for the menu
         */
        var _setScrolls = function() {
            try {
                var containerNode = AutoComplete.labelElmt.childNodes[0];
                var menuHeight = containerNode.offsetHeight;
                var totalMenuHeight = menuHeight + containerNode.scrollTop;
                var itemTopHeight = this.currentMenuItem.offsetTop;
                var itemHeight = this.currentMenuItem.offsetHeight;
                if (totalMenuHeight <= (itemTopHeight + itemHeight)) containerNode.scrollTop += menuHeight;
                else if ((totalMenuHeight - itemTopHeight) > menuHeight) containerNode.scrollTop -= menuHeight;
            } catch (exp) {}
        }
        /**
         * Create a div representing a menu entry div
         */
        var _createEntryDiv = function() {
            var entryDiv = document.createElement("div");
            entryDiv.className = "inactive-menu-entry";
            entryDiv.onmouseover = bindEvent(this._changeMenuEntry, this);
            entryDiv.onclick = bindEvent(this._selectionHandler, this);
            return entryDiv;
        }

        /**
         * Called onclick or on enter key press over an entry
         */
        var _selectionHandler = function(eventObject) {
            var divElmt = this.currentMenuItem;
            var currentDataObject = this.currentCacheObj.dataCache[this._getText(divElmt)];
            this.fillElmt.setValue(currentDataObject[this.fieldFillValue]);
            console.log("---------------------- Selected -------------------------");
            console.log("Selected " + this.fillElmt.getValue());
            this.clearMenu(eventObject);
            this._triggerSelectionEvent(divElmt);
            this.previousKeyCode = undefined;
        }

        /**
         * To trigger the user selection event
         * @param {Object} eventObject - div element corresponding to the entry
         */
        var _triggerSelectionEvent = function(divElmt) {
            if (!this.selectionHandler) {
                return;
            }
            var eventObj = _copyObject(this.currentCacheObj.dataCache[this._getText(divElmt)], {});
            eventObj.HTMLElement = divElmt;
            eventObj.sourceFieldId = this.inputFldId;
            this.selectionHandler(eventObj);
        }

        /**
         * Fills the menu from the cache if the data is already available there else triggers the webservice and
         * then fills the menu
         */
        var _triggerSearchProcess = function(searchText) {
            //console.log("searchText in _triggerSearchProcess : " + searchText);
            if (searchText == "") {
                this.clearMenu();
            } else if (this._isCached(searchText)) {
                //console.log("filling from cache for : " + searchText);
                this._fillFromCache(searchText);
            } else if (this.singleFire && this.wsFired) {
                this._parseResponse(this.response);
            } else {
                //console.log("firing service for  : " + searchText);		
                this._fireSearch(searchText);
            }
        }

        /**
         * Fill the menu by taking the menuNode from the cache. The cache is keyed based on the search text.
         * @param {Object} searchText - Key to the cache
         */
        var _fillFromCache = function(searchText) {
            if (AutoComplete.eventCaptureLayer.childNodes.length > 0) {
                this.clearMenu();
            }
            this.currentCacheObj = this._getCachedEntry(searchText);
            var numEntries = this.currentCacheObj.entryNode.childNodes.length;
            if (numEntries > 0) {
                this.currentCacheObj.entryNode.style.top = this.isGrid ? this._calculateMenuTop() : this.menuTop;
                if (this.fillElmt.value == searchText) {
                    AutoComplete.eventCaptureLayer.appendChild(this.currentCacheObj.entryNode);
                    this.currentMenuItem = this.currentCacheObj.entryNode.childNodes[0];
                    this._changeMenuEntry(this.currentMenuItem);
                    setLayerVisibility(true);
                    this.fillElmt.focus();
                }
            }
        }

        /**
         * Called when the user navigates using the keyboard, onmouseover an entry etc. To change the selection of the
         * entry to the divElmt that is passed to it
         * @param {Object} divElmt - The entry div that is to be made in focus now
         */
        var _changeMenuEntry = function(eventObject, direction) {
            try {
                var divElmt = eventObject.currentTarget || eventObject;
                this.currentMenuItem.className = 'inactive-menu-entry';
                divElmt.className = 'active-menu-entry';
                this.currentMenuItem = divElmt;
                scrollAdjust(divElmt, direction);
            } catch (ex) {}
        }

        /**
         * Fire the search webservice that should be fired to get the data corresponding to the text that the user
         * is typing. The webservice is fired from the model that has been set while creating the AutoComplete
         * @param {Object} searchText - The input to the webservice which will be the text for which the search has
         * to be conducted
         */
        var _fireSearch = function(searchText) {
            req = this.wsModel.getMethodRequest();
            this._fillInput(req);
            var bdiIndex = getUnusedBdi();
            AutoComplete.bdiDiv[bdiIndex].onresponse = (function(that, searchText, bdiIndex) {
                return function(eventObject) {
                    AutoComplete.bdiDiv[bdiIndex].inUse = false;
                    that._searchResponse(eventObject.response, searchText)
                }
            })(this, searchText, bdiIndex);
            //bindEvent(this._searchResponse, this, {searchText : searchText});
            AutoComplete.bdiDiv[bdiIndex].clear();
            AutoComplete.bdiDiv[bdiIndex].request = req;
            AutoComplete.bdiDiv[bdiIndex].inUse = true;
            AutoComplete.bdiDiv[bdiIndex].reset();
            //console.log("using bdi '" + bdiIndex + "' for searchText '" + searchText + "'");
            //console.log("request sent for : " + searchText);
        }

        /**
         * Function to fill the input in the request
         * @param {Object} request
         */
        var _fillInput = function(request) {
            for (var i = 0; i < this.wsInputObj.length; i++) {
                var currentInputObj = this.wsInputObj[i];
                if (this.isGrid && currentInputObj.isColumn) {
                    var rowNumber = this.tableObject.getIndex();
                    currentValue = this._trim(currentInputObj.element[rowNumber].value);
                } else {
                    var currentValue = this._trim(currentInputObj.element.value);
                }
                cordys.setTextContent(cordys.selectXMLNode(request, ".//*[local-name() = '" + currentInputObj.tagName + "']"), currentValue);
            }
            return request;
        }

        /**
         * To return the text content inside a div element
         * @param {Object} divElmt
         */
        var _getText = function(divElmt) {
            return divElmt.innerText || divElmt.textContent;
        }

        /**
         * Clear the menu and wipe off all the entries
         */
        var clearMenu = function(eventObject) {
            eventObject && cordys.stopPropagation(eventObject);
            if (this.currentMenuItem) {
                this.currentMenuItem.className = 'inactive-menu-entry';
            }
            AutoComplete.eventCaptureLayer.innerHTML = "";
            setLayerVisibility(false);
        }

        /**
         * To set the selection handler
         */
        var setSelectionHandler = function(handler) {
            this.selectionHandler = handler;
        }

        /**
         * To handle the insert event in the grid. The newly created row has to be cached in the columnsArray as well as
         * to the input objects
         */
        var _insertHandler = function() {
            this._cacheSingleColumn(this.columnNumber, this.columnsArray, this.keyEventHandler);
            for (var i = 0; i < this.wsInputObj.length; i++) {
                var currentInputObj = this.wsInputObj[i];
                if (currentInputObj.isColumn) {
                    if (!currentInputObj.element) {
                        currentInputObj.element = [null];
                    }
                    this._cacheSingleColumn(currentInputObj.fldId, currentInputObj.element);
                }
            }
        }

        /**
         * Handler to deal with delete and refreh events of the grid
         */
        var _deleteAndRefreshHandler = function() {
            this._cacheInputElements();
            this._setGridKeyEvents();
        }

        /**
         * To clear the cache of stored requests
         */
        var clearCache = function() {
            this.cacheObj = undefined;
        }

        return {
            "setSelectionHandler": setSelectionHandler,
            "clearMenu": clearMenu,
            "clearCache": clearCache,
            "_searchResponse": _searchResponse,
            "_changeMenuEntry": _changeMenuEntry,
            "_selectionHandler": _selectionHandler,
            "_keyEvent": _keyEvent,
            "_setKeyEvents": _setKeyEvents,
            "_fireSearch": _fireSearch,
            "_fillFromCache": _fillFromCache,
            "_triggerSearchProcess": _triggerSearchProcess,
            "_triggerSelectionEvent": _triggerSelectionEvent,
            "_createEntryDiv": _createEntryDiv,
            "_setScrolls": _setScrolls,
            "_newCacheObject": _newCacheObject,
            "_menuNavigate": _menuNavigate,
            "_pushToDataCache": _pushToDataCache,
            "_cacheInputElements": _cacheInputElements,
            "_fillInput": _fillInput,
            "_parseResponse": _parseResponse,
            "_getCurrentCacheKey": _getCurrentCacheKey,
            "_getCachedEntry": _getCachedEntry,
            "_isCached": _isCached,
            "_trim": _trim,
            "_getText": _getText,
            "_setInputElement": _setInputElement,
            "_setSingleFieldKeyEvents": _setSingleFieldKeyEvents,
            "_setGridKeyEvents": _setGridKeyEvents,
            "_cacheFullColumns": _cacheFullColumns,
            "_insertHandler": _insertHandler,
            "_deleteAndRefreshHandler": _deleteAndRefreshHandler,
            "_cacheSingleColumn": _cacheSingleColumn,
            "_calculateMenuTop": _calculateMenuTop,
            "_getRearrangedResult": _getRearrangedResult
        }
    })();

    //Controls the number of BDIs created
    var bdiCount = 10;

    /**
     * Adds some CSS classes to the current document to be used by the menu div
     */

    function setStyle() {
        var style = '.menu-div{overflow:auto;overflow-y:auto;border:solid 1px #96CAEA;color:black;background-color:#C4EBF6;position:absolute;}\
	               .menu-div div{margin-left:1px;margin-right:1px;}\
	               .active-menu-entry{background-color:#FFF1B7;color:black;}\
	               .inactive-menu-entry{background-color:C4EBF6;color:black;}';
        var styleElmt = document.createElement("style");
        styleElmt.setAttribute("type", "text/css");
        if (styleElmt.styleSheet) //for IE
        styleElmt.styleSheet.cssText = style;
        else //the world
        styleElmt.innerHTML = style;
        document.getElementsByTagName("head")[0].appendChild(styleElmt);
    }

    /**
     * Adds the BOP4 BDI library to the document for firing webservices
     */

    function setupBusDataIsland() {
        AutoComplete.bdiDiv = [];
        for (var i = 0; i < bdiCount; i++) {
            AutoComplete.bdiDiv[i] = document.createElement("div");
            document.body.appendChild(AutoComplete.bdiDiv[i]);
            window.application.addType(AutoComplete.bdiDiv[i], "wcp.library.data.BusDataIsland");
            AutoComplete.bdiDiv[i].async = true;
            AutoComplete.bdiDiv[i].id = 'bdiId' + i;
        }
    }

    function getUnusedBdi() {
        for (var i = 0; i < bdiCount; i++) {
            if (!AutoComplete.bdiDiv[i].inUse) {
                return i;
            }
        }
    }

    /**
     * To create an event capture layer for the menu
     */

    function setupEventCaptureLayer() {
        var element = AutoComplete.eventCaptureLayer = document.createElement("div");
        element.style.width = "100%";
        element.style.height = "100%";
        element.style.zIndex = "10000";
        element.style.position = "absolute";
        element.style.top = "0px";
        element.style.display = "none";
        element.id = "eventCaptureLayer";

        var desktopDocument = top.document.getElementById("desktop").contentWindow.document;
        var dialogRootFrame = desktopDocument.getElementById("ufo_dialog_CordysRoot");
        if (!dialogRootFrame) {
            var breadCrumDocument = desktopDocument.getElementById("breadcrum").contentWindow.document;
            var mainDiv = breadCrumDocument.getElementById("main");
            var lastDivIndex = mainDiv.childNodes.length;
            dialogRootFrame = mainDiv.childNodes[lastDivIndex - 1].childNodes[0];
        }
        var xbody = dialogRootFrame.contentWindow.document.getElementById("xbody_group");
        xbody.appendChild(element);
    }

    /**
     * Initialization for the auto complete menu. This function has to be called first before creating any menus.
     * A label has to be added in the xform and its field ID has to be passed to this function initially.
     * @param {Object} labelId - field id of the dummy label that acts as a container for all the menu divs
     */
    (function init() {
        setStyle();
        setupBusDataIsland();
        setupEventCaptureLayer();
        AutoComplete.isMenuVisible = false;
    })();

    /**
     * To set the visibility of the event capture layer. Sets the isMenuVisible property
     * to true if the menu is visible.
     * @param {Object} isVisible - visible if true
     */

    function setLayerVisibility(isVisible) {
        AutoComplete.eventCaptureLayer.style.display = isVisible ? "block" : "none";
        AutoComplete.isMenuVisible = isVisible;
    }

    /**
     * Returns a function variable that calls the eventHandler function with the object passed as the this object. Used
     * in setting the events like keyup, keydown and WS Response handlers so that the AutoComplete object is
     * available in the handlers
     * @param {Object} eventHandler - handler function to be called
     * @param {Object} thisObj - object to be used as the this object for this handler
     */

    function bindEvent(eventHandler, thisObj, userArguments) {
        return function(eventObject) {
            eventObject = eventObject || window.event || {};
            userArguments && (eventObject.userArguments = userArguments);
            eventHandler.call(thisObj, eventObject || window.event);
        }
    }

    /**
     * Appends "px" to the end of a string if it is not already added.
     * @param {Object} str
     */

    function addPx(str) {
        return (/px$/.test(str)) ? str : (str + "px");
    }

    /**
     * To adjust the scroll in the list while the user scrolls down using the keyboard
     * @param {Object} entry - The current entry that the user has selected
     * @param {Object} direction - "up" if the user scrolled down, "down" elsewise
     */

    function scrollAdjust(entry, direction) {
        var container = entry.parentNode;
        if (!direction) {
            return;
        } else if (direction == "up") {
            var scrollTop = entry.offsetTop;
        } else {
            scrollTop = entry.offsetTop + entry.offsetHeight - container.offsetHeight;
        }
        container.scrollTop = scrollTop;
    }

    function formatHTML(htmlString, searchText) {
        //Removing trailing comma. Replacing double spaces with single space
        htmlString = getBold(htmlString, searchText).replace(/\,\s$/, "").replace(/\s\s/g, " ");
        //Removing leading and trailing spaces
        htmlString = htmlString.replace(/^\s+|\s+$/g, "");
        return htmlString;
    }

    /**
     * To make the searched string bold in the inner HTML
     */
    var getBold = function(innerHTML, searchText) {
        var regex = new RegExp("(.*?)(" + searchText.replace(/^\s+|\s+$/g, "") + ")(.*)", "i");
        var replacedString = innerHTML.replace(regex, "$1<b>$2</b>$3");
        return replacedString;
    }

    return AutoComplete;
})();
scriptCallback && scriptCallback("Auto Complete");